#!/bin/sh

# -----------------------------------------------------------------------
#  gentramp.sh - Copyright (c) 2010-2011, Plausible Labs Cooperative, Inc.
#
#  Trampoline Page Generator
#  Author: Landon Fuller <landonf@plausible.coop>
#
#  Permission is hereby granted, free of charge, to any person obtaining
#  a copy of this software and associated documentation files (the
#  ``Software''), to deal in the Software without restriction, including
#  without limitation the rights to use, copy, modify, merge, publish,
#  distribute, sublicense, and/or sell copies of the Software, and to
#  permit persons to whom the Software is furnished to do so, subject to
#  the following conditions:
#
#  The above copyright notice and this permission notice shall be included
#  in all copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
#  NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
#  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
#  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
#  DEALINGS IN THE SOFTWARE.
#  -----------------------------------------------------------------------

PROGNAME=$0

INPUT_FILE_PATH="$1"
INPUT_FILE_BASE="`basename -a $(sed s/\.[^.]*$// <<< $INPUT_FILE_PATH)`"
CURRENT_ARCH="$2"
OUTPUT_DIR="$3"
SRC_C_OUTPUT="${OUTPUT_DIR}/${INPUT_FILE_BASE}_config.c"
SRC_OUTPUT="${OUTPUT_DIR}/${INPUT_FILE_BASE}.s"
HEADER_OUTPUT="${OUTPUT_DIR}/${INPUT_FILE_BASE}.h"

# Default implementation
trampoline_prefix () {
    return 0
}

# Import the trampoline definition
. ${INPUT_FILE_PATH}

check_required () {
    local name=$1
    eval "local var=\${$1}"
    
    if [ -z "${var}" ]; then
        echo "Required variable ${name} not defined."
        exit 1
    fi
}

check_required ARCH
check_required PAGE_SIZE
check_required PAGE_NAME

# Write a header line
header () {
    echo "$1" >> "${HEADER_OUTPUT}"
}

# Write a C source line
src () {
    echo "$1" >> "${SRC_C_OUTPUT}"
}

# Flush the assembler output buffer to disk
ASM_BUFFER=""
asm_flush () {
    echo "${ASM_BUFFER}" >> "${SRC_OUTPUT}"
    asm_discard
}

# Write the assembler buffer to disk, but don't discard the contents
asm_write () {
    echo "${ASM_BUFFER}" >> "${SRC_OUTPUT}"
}

# Discard the current assembler output buffer
asm_discard () {
    ASM_BUFFER=''
    return 0;
}

# Append data to the assembler output buffer
asm () {
    local line=""
    while read -r line; do
        ASM_BUFFER+=$line
        ASM_BUFFER+="\n"
    done
}

# Compute the assembled size of the current assembler buffer
compute_asm_size () {
    # Create the temporary assembler file
    local output=".globl _byte_count_start\n"
    output+="_byte_count_start:\n"
    output+="${ASM_BUFFER}"
    output+=".globl _byte_count_end\n"
    output+="_byte_count_end:\n"

    local tempfile=`mktemp /tmp/as_bytecount.XXXXXXXX`
    echo "${output}" | as -arch "${CURRENT_ARCH}" -o "${tempfile}" -
    if [ $? != 0 ]; then
        echo "Assembling the trampoline failed"
        exit 1
    fi

    local byte_size=`nm -t d -P "${tempfile}" | grep ^_byte_count_end | awk '{print $3}'`
    rm -f "${tempfile}"

    echo $byte_size
}


# Write out the page header
write_page_decl () {
    # Calculate the required alignment
    local align=`perl -l -e "print log(${PAGE_SIZE})/log(2)"`
    asm << EOF
        # GENERATED CODE - DO NOT EDIT"
        # This file was generated by $PROGNAME on `date`

        # Write out the trampoline table, aligned to the page boundary
        .text
        .align ${align}
        .globl _${PAGE_NAME}
        _${PAGE_NAME}:
EOF
}

main () {
    if [ -e "${SRC_OUTPUT}" -a -e "${SRC_C_OUTPUT}" -a -e "${HEADER_OUTPUT}" ] ; then
	return;
    fi
    echo '' > "${SRC_OUTPUT}"
    echo '' > "${SRC_C_OUTPUT}"
    echo '' > "${HEADER_OUTPUT}"
    
    # Write out the trampoline header file
    header "extern void *${PAGE_NAME};"
    header "extern struct pl_trampoline_table_config ${PAGE_NAME}_config;" 

    # Don't generate the sources for the incorrect arch
    if [ "${ARCH}" != "${CURRENT_ARCH}" ]; then
        return
    fi

    # Determine the trampoline prefix size
    trampoline_prefix
    local prefix_size=$(compute_asm_size)
    asm_discard

    # Compute the size of the remaining code page.
    local page_avail=`expr $PAGE_SIZE - $prefix_size`

    # Determine the trampoline size
    trampoline
    local tramp_size=$(compute_asm_size)
    asm_discard
    if [ "${tramp_size}" = 0 ]; then
        echo "Error occured calculating trampoline size; received size of 0"
        exit 1
    fi

    # Compute the number of of available trampolines. 
    local trampoline_count=`expr $page_avail / $tramp_size`
    echo "Prefix size: ${prefix_size}"
    echo "Trampoline size: ${tramp_size}"
    echo "Trampolines per page: ${trampoline_count}"

    # Write out the page declaration
    write_page_decl
    asm_flush

    # Write out the prefix
    trampoline_prefix
    asm_flush

    # Write out the trampolines
    trampoline
    local i=0
    while [ $i -lt ${trampoline_count} ]; do
        asm_write
        local i=`expr $i + 1`
    done
    asm_discard
    
    # Write out the table configuration
    local config_src=`cat << EOF    
        #include "trampoline_table.h"

        extern void *${PAGE_NAME};
        pl_trampoline_table_config ${PAGE_NAME}_config = {
            .trampoline_size = ${tramp_size},
            .page_offset = ${prefix_size},
            .trampoline_count = ${trampoline_count},
            .template_page = &${PAGE_NAME}
        };
EOF`
    src "${config_src}"
}

main
